package logger

import (
	"context"
	"os"
	"sync"
	"time"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

/**
	doc
		https://godoc.org/go.uber.org/zap
    link
		https://fengberlin.github.io/post/go-grpc-rest-microservice-part3/
		https://www.jianshu.com/p/b0de3b46e63f
*/

var (
	// Log 是全局日志记录器
	Log *zap.SugaredLogger
	// onceInit保证仅初始化全局日志记录器一次
	onceInit sync.Once
	// default log level is info
	defaultLogLevel = 0
)

func init() {
	setLog(defaultLogLevel)
}

// customTimeEncoder 编码自定义格式的时间
// 这个例子教我们如何定制zap默认功能
func customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
}

// Init 通过输入参数初始化日志
// lvl - 全局日志级别：Debug（-1），Info（0），Warn（1），Error（2），DPanic（3），Panic（4），Fatal（5）
// timeFormat - 默认使用空字符串的记录器的自定义时间格式
func Init(lvl int) {
	onceInit.Do(func() {
		setLog(lvl)
	})
}

func Info(args ...interface{}) {
	Log.Info(args...)
}

func Infof(template string, args ...interface{}) {
	Log.Infof(template, args...)
}

func Infow(msg string, keysAndValues ...interface{}) {
	Log.Infow(msg, keysAndValues...)
}

const KeyRequestID = "X-Request-ID"

func With(args ...interface{}) *zap.SugaredLogger {
	var sugar *zap.SugaredLogger
	for {
		if len(args) == 1 {
			if ctx, ok := args[0].(context.Context); ok && ctx != nil && ctx.Value(KeyRequestID) != nil {
				if val := ctx.Value(KeyRequestID).(string); val != "" {
					sugar = Log.With(zap.String("requestID", val))
					break
				}
				sugar = Log.With()
				break
			}
		}
		sugar = Log.With(args...)

		break
	}
	return sugar.Desugar().WithOptions(zap.AddCallerSkip(-1)).Sugar()
}

func Debug(args ...interface{}) {
	Log.Debug(args...)
}

func Debugf(template string, args ...interface{}) {
	Log.Debugf(template, args...)
}

func Debugw(msg string, keysAndValues ...interface{}) {
	Log.Debugw(msg, keysAndValues...)
}

func Error(args ...interface{}) {
	Log.Error(args...)
}

func Errorf(template string, args ...interface{}) {
	Log.Errorf(template, args...)
}

func Errorw(msg string, keysAndValues ...interface{}) {
	Log.Errorw(msg, keysAndValues...)
}

func ErrorStack(msg ...interface{}) {
	With(zap.Stack("stack")).Error(msg...)
}

func ErrorStackf(template string, args ...interface{}) {
	With(zap.Stack("stack")).Errorf(template, args...)
}

func ErrorStackw(msg string, keysAndValues ...interface{}) {
	With(zap.Stack("stack")).Errorw(msg, keysAndValues...)
}

func Warn(args ...interface{}) {
	Log.Warn(args...)
}

func Warnf(template string, args ...interface{}) {
	Log.Warnf(template, args...)
}

//  s.With(keysAndValues).Debug(msg)
func Warnw(msg string, keysAndValues ...interface{}) {
	Log.Warnw(msg, keysAndValues...)
}

func Fatal(args ...interface{}) {
	Log.Fatal(args...)
}

func Panic(args ...interface{}) {
	Log.Panic(args...)
}

func Fatalf(template string, args ...interface{}) {
	Log.Fatalf(template, args...)
}

func Fatalw(msg string, keysAndValues ...interface{}) {
	Log.Fatalw(msg, keysAndValues...)
}

func setLog(lvl int) {
	// 首先，定义我们的级别处理逻辑。
	globalLevel := zapcore.Level(lvl)

	// 高优先级输出是标准错误，低优先级输出是标准输出。
	// 它对Kubernetes部署很有用。
	// Kubernetes默认将os.Stdout日志项解释为INFO和os.Stderr日志项解释为ERROR。
	highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= zapcore.ErrorLevel
	})
	lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
		return lvl >= globalLevel && lvl < zapcore.ErrorLevel
	})

	// 配置控制台输出。
	var useCustomTimeFormat bool
	ecfg := zap.NewProductionEncoderConfig()
	ecfg.EncodeTime = customTimeEncoder
	useCustomTimeFormat = true

	wOut := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "/tmp/x-out.log",
		MaxSize:    300, // 每个日志文件保存的最大尺寸 单位：M
		MaxBackups: 3,   // 日志文件最多保存多少个备份
		MaxAge:     28,  // 文件最多保存多少天
	})

	wErr := zapcore.AddSync(&lumberjack.Logger{
		Filename:   "/tmp/x-error.log",
		MaxSize:    300, // megabytes
		MaxBackups: 3,
		MaxAge:     28, // days
	})

	// 开启开发模式，堆栈跟踪
	caller := zap.AddCaller()
	// 跳过最后一级堆栈
	callerSkip := zap.AddCallerSkip(1)
	// 开启文件及行号
	development := zap.Development()

	consoleEncoder := zapcore.NewJSONEncoder(ecfg)

	// 将输出，编码器和日志级别处理功能加入zapcore
	core := zapcore.NewTee(
		zapcore.NewCore(consoleEncoder,
			zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), wOut),
			lowPriority),
		zapcore.NewCore(consoleEncoder,
			zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stderr), wErr),
			highPriority),
	)

	Log = zap.New(core, caller, callerSkip, development).Sugar()

	// RedirectStdLog将标准库的包全局记录器的输出重定向到InfoLevel上提供的记录器。
	// 由于zap已经处理了调用者注释，时间戳等，它会自动禁用标准库的注释和前缀。
	zap.RedirectStdLog(Log.Desugar())
	if !useCustomTimeFormat {
		Log.Warn("time format for logger is not provided - use zap default")
	}
}
